#!/usr/bin/python3

import time
import smbus

# BMP085 default address.
BMP180_I2CADDR = 0x77

# Operating Modes
BMP180_ULTRALOWPOWER = 0
BMP180_STANDARD = 1
BMP180_HIGHRES = 2
BMP180_ULTRAHIGHRES = 3

# BMP085 Registers
BMP180_CAL_AC1 = 0xAA  # R  Calibration data (16 bits)
BMP180_CAL_AC2 = 0xAC  # R  Calibration data (16 bits)
BMP180_CAL_AC3 = 0xAE  # R  Calibration data (16 bits)
BMP180_CAL_AC4 = 0xB0  # R  Calibration data (16 bits)
BMP180_CAL_AC5 = 0xB2  # R  Calibration data (16 bits)
BMP180_CAL_AC6 = 0xB4  # R  Calibration data (16 bits)
BMP180_CAL_B1 = 0xB6  # R  Calibration data (16 bits)
BMP180_CAL_B2 = 0xB8  # R  Calibration data (16 bits)
BMP180_CAL_MB = 0xBA  # R  Calibration data (16 bits)
BMP180_CAL_MC = 0xBC  # R  Calibration data (16 bits)
BMP180_CAL_MD = 0xBE  # R  Calibration data (16 bits)
BMP180_CONTROL = 0xF4
BMP180_TEMPDATA = 0xF6
BMP180_PRESSUREDATA = 0xF6

# Commands
BMP180_READTEMPCMD = 0x2E
BMP180_READPRESSURECMD = 0x34


class BMP180(object):

    def __init__(self, address=BMP180_I2CADDR, mode=BMP180_STANDARD):

        self._mode = mode
        self._address = address
        self._bus = smbus.SMBus(1)

        # Load calibration values.
        self._load_calibration()

    def _read_byte(self, cmd):
        return self._bus.read_byte_data(self._address, cmd)

    def _read_u16(self, cmd):
        msb = self._bus.read_byte_data(self._address, cmd)
        lsb = self._bus.read_byte_data(self._address, cmd + 1)
        return (msb << 8) + lsb

    def _read_s16(self, cmd):
        result = self._read_u16(cmd)
        if result > 32767:
            result -= 65536
        return result

    def _write_byte(self, cmd, val):
        self._bus.write_byte_data(self._address, cmd, val)

    def _load_calibration(self):
        # load calibration
        self.cal_AC1 = self._read_s16(BMP180_CAL_AC1)  # INT16
        self.cal_AC2 = self._read_s16(BMP180_CAL_AC2)  # INT16
        self.cal_AC3 = self._read_s16(BMP180_CAL_AC3)  # INT16
        self.cal_AC4 = self._read_u16(BMP180_CAL_AC4)  # UINT16
        self.cal_AC5 = self._read_u16(BMP180_CAL_AC5)  # UINT16
        self.cal_AC6 = self._read_u16(BMP180_CAL_AC6)  # UINT16
        self.cal_B1 = self._read_s16(BMP180_CAL_B1)  # INT16
        self.cal_B2 = self._read_s16(BMP180_CAL_B2)  # INT16
        self.cal_MB = self._read_s16(BMP180_CAL_MB)  # INT16
        self.cal_MC = self._read_s16(BMP180_CAL_MC)  # INT16
        self.cal_MD = self._read_s16(BMP180_CAL_MD)  # INT16

    def read_raw_temp(self):
        # Reads the raw (uncompensated) temperature from the sensor.
        self._write_byte(BMP180_CONTROL, BMP180_READTEMPCMD)
        time.sleep(0.005)  # Wait 5ms
        msb = self._read_byte(BMP180_TEMPDATA)
        lsb = self._read_byte(BMP180_TEMPDATA + 1)
        raw = (msb << 8) + lsb
        return raw

    def read_raw_pressure(self):
        # Reads the raw (uncompensated) pressure level from the sensor.
        self._write_byte(BMP180_CONTROL, BMP180_READPRESSURECMD + (self._mode << 6))
        if self._mode == BMP180_ULTRALOWPOWER:
            time.sleep(0.005)
        elif self._mode == BMP180_HIGHRES:
            time.sleep(0.014)
        elif self._mode == BMP180_ULTRAHIGHRES:
            time.sleep(0.026)
        else:
            time.sleep(0.008)
        msb = self._read_byte(BMP180_PRESSUREDATA)
        lsb = self._read_byte(BMP180_PRESSUREDATA + 1)
        x_lsb = self._read_byte(BMP180_PRESSUREDATA + 2)
        raw = ((msb << 16) + (lsb << 8) + x_lsb) >> (8 - self._mode)
        return raw

    def read_temperature(self):
        # Gets the compensated temperature in degrees celsius.
        ut = self.read_raw_temp()
        x1 = ((ut - self.cal_AC6) * self.cal_AC5) >> 15
        x2 = (self.cal_MC << 11) / (x1 + self.cal_MD)
        b5 = x1 + x2
        temp = (int(b5 + 8) >> 4) / 10.0
        return temp

    def read_pressure(self):
        # Gets the compensated pressure in Pascals.
        ut = self.read_raw_temp()
        up = self.read_raw_pressure()
        x1 = ((ut - self.cal_AC6) * self.cal_AC5) >> 15
        x2 = (self.cal_MC << 11) / (x1 + self.cal_MD)
        b5 = x1 + x2

        # Pressure Calculations
        b6 = b5 - 4000
        x1 = (self.cal_B2 * int(b6 * b6) >> 12) >> 11
        x2 = int(self.cal_AC2 * b6) >> 11
        x3 = x1 + x2
        b3 = (((self.cal_AC1 * 4 + x3) << self._mode) + 2) / 4
        x1 = int(self.cal_AC3 * b6) >> 13
        x2 = int(self.cal_B1 * (int(b6 * b6) >> 12)) >> 16
        x3 = int((x1 + x2) + 2) >> 2
        b4 = int(self.cal_AC4 * (x3 + 32768)) >> 15
        b7 = (up - b3) * (50000 >> self._mode)

        if b7 < 0x80000000:
            p = (b7 * 2) / b4
        else:
            p = (b7 / b4) * 2
        x1 = (int(p) >> 8) * (int(p) >> 8)
        x1 = int(x1 * 3038) >> 16
        x2 = int(-7357 * p) >> 16
        p = p + (int(x1 + x2 + 3791) >> 4)

        return p

    def read_altitude(self, sea_level_pa=101325.0):
        # Calculates the altitude in meters.
        # Calculation taken straight from section 3.6 of the datasheet.
        pressure = float(self.read_pressure())
        altitude = 44330.0 * (1.0 - pow(pressure / sea_level_pa, (1.0 / 5.255)))
        return altitude

    def read_sea_level_pressure(self, altitude_m=0.0):
        # Calculates the pressure at sealevel when given a known altitude in
        # meters. Returns a value in Pascals.
        pressure = float(self.read_pressure())
        p0 = pressure / pow(1.0 - altitude_m / 44330.0, 5.255)
        return p0
